Completed
Push — master ( a8bb1b...85a8aa )
by greg
01:40
created

Page.js ➔ ???   D

Complexity

Conditions 15
Paths 26

Size

Total Lines 126

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
c 9
b 0
f 0
nc 26
dl 0
loc 126
rs 4.9121
cc 15
nop 4

1 Function

Rating   Name   Duplication   Size   Complexity  
A Page.js ➔ ... ➔ ??? 0 14 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Page.js ➔ ??? often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import Handlebars from 'handlebars'
2
import path from 'path'
3
import fse from 'fs-extra'
4
5
import {
6
  abeEngine,
7
  cmsData,
8
  cmsTemplates,
9
  config,
10
  Hooks,
11
  Manager
12
} from '../'
13
14
/**
15
 * Page class
16
 * manage HTML generation for page template
17
 */
18
export default class Page {
19
20
  /**
21
   * Create new page object
22
   * @param  {Object} params req.params from express route
0 ignored issues
show
Documentation introduced by
The parameter params does not exist. Did you maybe forget to remove this comment?
Loading history...
23
   * @param  {Object} i18n translation
0 ignored issues
show
Documentation introduced by
The parameter i18n does not exist. Did you maybe forget to remove this comment?
Loading history...
24
   * @param  {Function} callback 
0 ignored issues
show
Documentation introduced by
The parameter callback does not exist. Did you maybe forget to remove this comment?
Loading history...
25
   * @param  {Boolean} onlyHTML default = false, if true HTML content will contains abe attributes
26
   * @return {String} HTML page as string
27
   */
28
  constructor(templateId, template, json, onlyHTML = false) {
29
    // HOOKS beforePageJson
30
    json = Hooks.instance.trigger('beforePageJson', json)
31
32
    if(typeof Handlebars.templates[templateId] !== 'undefined' && 
33
        Handlebars.templates[templateId] !== null && 
34
        config.files.templates.precompile
35
      ){
36
37
      template = Handlebars.templates[templateId]
38
      this.html = template(json, {data: {intl: config.intlData}})
39
40
      //console.log('precompile')
41
42
    } else {
43
44
      this._onlyHTML = onlyHTML
45
      this.template = template
46
      this.HbsTemplatePath = path.join(config.root, config.templates.url, 'hbs/'+templateId+'.hbs')
47
48
      abeEngine.instance.content = json
49
      
50
      // This pattern finds all abe tags which are not enclosed in a html tag attribute
51
      // it finds this one: <title>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</title>
52
      // it excludes this one: <meta name="description" content='{{abe type="text" key="meta_description" desc="Meta description" tab="Meta" order="4100"}}"/> 
53
      this.abePattern = /[^"']({{abe.*?type=[\'|\"][text|rich|textarea]+[\'|\"][\s\S].*?}})/g
54
55
      // This pattern finds all abe tags enclosed in a HTML tag attribute
56
      this.abeAsAttributePattern = /( [A-Za-z0-9\-\_]+=["|']{1}{{abe.*?}})/g
57
58
      // This pattern finds all {{#each ...}}...{{/each}} blocks
59
      this.eachBlockPattern = />\s*(\{\{#each (\r|\t|\n|.)*?\/each\}\})/g
60
61
      // This pattern finds all {{#each ...}}...{{/each}} blocks
62
      this.blockPattern = /(\{\{#each.*\}\}[\s\S]*?\{\{\/each\}\})/g
63
64
      // Remove text with attribute "visible=false"
65
      this._removeHidden()
66
    
67
      if(!this._onlyHTML) {
68
69
        // Surrounds each Abe tag (which are text/rich/textarea and not in html attribute) with <abe> tag
70
        // ie. <title><abe>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</abe></title>
71
        this._encloseAbeTag()
72
      }
73
74
      // je rajoute les index pour chaque bloc lié à un each
75
      this._indexEachBlocks()
76
      
77
      if(!this._onlyHTML){
78
79
        // Je maj les attributs associés aux Abe qui sont dans des attributs de tag HTML
80
        this._updateAbeAsAttribute()
81
82
        // je rajoute les attributs pour les tags Abe (qui ne sont pas dans un attribut HTML)
83
        this._updateAbeAsTag()
84
85
        // Don't know what it does...
86
        var source = config.source.name
87
        if(typeof json[source] !== 'undefined' && json[source] !== null) {
88
          var keys = Object.keys(json[source])
89
          
90
          for(var i in keys) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
91
            var replaceEach = new RegExp(`<!-- \\[\\[${keys[i]}\\]\\][\\s\\S]*?-->`, 'g')
92
            this.template = this.template.replace(replaceEach, '')
93
94
            var patAttrSource = new RegExp(' ([A-Za-z0-9\-\_]+)=["|\'].*?({{' + keys[i] + '}}).*?["|\']', 'g')
95
            var patAttrSourceMatch = this.template.match(patAttrSource)
96
97
            if(patAttrSourceMatch != null) {
98
              let checkEscapedRegex = /["|'](.*?)["|']/
99
              let patAttrSourceInside = new RegExp('(\\S+)=["\']?((?:.(?!["\']?\\s+(?:\\S+)=|[>"\']))+.)["\']?({{' + keys[i] + '}}).*?["|\']', 'g')
100
              Array.prototype.forEach.call(patAttrSourceMatch, (pat) => {
101
                let patAttrSourceCheck = patAttrSourceInside.exec(pat)
102
                if(patAttrSourceCheck != null) {
103
                  
104
                  let checkEscaped = checkEscapedRegex.exec(patAttrSourceCheck[0])
105
                  if(checkEscaped != null && checkEscaped.length > 0) {
106
                    checkEscaped = escape(checkEscaped[1])
107
                    this.template = this.template.replace(
108
                      patAttrSourceCheck[0],
109
                      ` data-abe-attr="${patAttrSourceCheck[1]}" data-abe-attr-escaped="${checkEscaped}" data-abe="${keys[i]}" ${patAttrSourceCheck[0]}`
0 ignored issues
show
introduced by
The variable i is changed by the for-each loop on line 90. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
110
                    )
111
                  }
112
                }
113
              })
114
            }
115
116
            var eachSource = new RegExp(`({{#each ${keys[i]}}[\\s\\S a-z]*?{{\/each}})`, 'g')
117
            var matches = this.template.match(eachSource)
118
            if(typeof matches !== 'undefined' && matches !== null) {
119
              Array.prototype.forEach.call(matches, (match) => {
120
                this.template = this.template.replace(match, `${match}<!-- [[${keys[i]}]] ${cmsTemplates.encodeAbeTagAsComment(match)} -->`)
0 ignored issues
show
introduced by
The variable i is changed by the for-each loop on line 90. Only the value of the last iteration will be visible in this function if it is called outside of the loop.
Loading history...
121
              })
122
            }
123
          }
124
        }
125
      }
126
     
127
      this._addSource(json)
128
129
      // We remove the {{abe type=data ...}} from the text 
130
      this.template = cmsData.source.removeDataList(this.template)
131
132
      // It's time to replace the [index] by {{@index}} (concerning each blocks)
133
      this.template = this.template.replace(/\[index\]\./g, '{{@index}}-')
134
135
      if(config.files.templates.precompile){
136
        // Let's persist the precompiled template for future use (kind of cache)
137
        fse.writeFileSync(this.HbsTemplatePath, Handlebars.precompile(this.template), 'utf8')
138
        Manager.instance.addHbsTemplate(templateId)
139
      }
140
141
      // I compile the text
142
      var compiledTemplate = Handlebars.compile((!this._onlyHTML) ? cmsTemplates.insertDebugtoolUtilities(this.template) : this.template)
143
144
      // I create the html page ! yeah !!!
145
      this.html = compiledTemplate(json, {data: {intl: config.intlData}})
146
    }
147
148
    if(this._onlyHTML) {
149
      this.html = Hooks.instance.trigger('afterPageSaveCompile', this.html, json)
150
    }else {
151
      this.html = Hooks.instance.trigger('afterPageEditorCompile', this.html, json)
152
    }
153
  }
154
155
  _updateAbeAsAttribute() {
156
    var match
157
    while (match = this.abeAsAttributePattern.exec(this.template)) { // While regexp match {{attribut}}, ex: link, image ...
158
      if(cmsData.regex.isSingleAbe(match[0], this.template)){
159
        var more_attr = ''
160
        var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
161
        this.template = this.template.replace(
162
          new RegExp(match[0]),
163
          ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + (match[0].split('=')[0]).trim() + '"' +
164
          ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '"' +
165
          more_attr + match[0].replace('}}', ' has-abe=1}}')
166
        )
167
      }
168
    }
169
170
    return this
171
  }
172
173
  _updateAbeAsTag() {
174
    var match
175
    while (match = this.abePattern.exec(this.template)) {
176
      var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
177
      this.template = this.template.replace(
178
        cmsData.regex.escapeTextToRegex(match[0], 'g'),
179
        ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '" ' + match[0]
180
      )
181
    }
182
183
    return this
184
  }
185
  
186
  /**
187
   * [_indexEachBlocks description]
188
   * @param  {[type]} text   [description]
0 ignored issues
show
Documentation introduced by
The parameter text does not exist. Did you maybe forget to remove this comment?
Loading history...
189
   * @param  {[type]} blocks [description]
0 ignored issues
show
Documentation introduced by
The parameter blocks does not exist. Did you maybe forget to remove this comment?
Loading history...
190
   * @return {[type]}        [description]
191
   */
192
  _indexEachBlocks() {
193
    // create an array of {{each}} blocks
194
    var blocks = this._splitEachBlocks()
195
196
    Array.prototype.forEach.call(blocks, (block) => {
197
      var key = block.match(/#each (.*)\}\}/)
198
      key = key[1]
199
      var match
200
201
      if(!this._onlyHTML) {
202
203
        var voidData = {}
204
        voidData[key] = [{}]
205
        var blockCompiled = Handlebars.compile(block.replace(/{{abe (.*?)}}/g, '[[abe $1]]').replace(new RegExp(`\\.\\.\/${config.meta.name}`, 'g'), config.meta.name))
206
        var blockHtml = blockCompiled(voidData, {data: {intl: config.intlData}}).replace(/\[\[abe (.*?)\]\]/g, '{{abe $1}}')
207
208
        // je rajoute un data-abe-block avec index sur tous les tags html du bloc each
209
        var textEachWithIndex = block.replace(/(<(?![\/])[A-Za-z0-9!-]*)/g, '$1 data-abe-block="' + key + '{{@index}}"')
210
211
        // je remplace le block dans le texte par ça
212
        this.template = this.template.replace(block, textEachWithIndex + `<!-- [[${key}]] ${cmsTemplates.encodeAbeTagAsComment(blockHtml)} -->`)
213
      }
214
215
      // Pour chaque tag Abe, je mets en forme ce tag avec des data- supplémentaires
216
      while (match = this.abePattern.exec(block)) {
217
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length)
218
      }
219
220
      // Pour chaque tag Abe attribut de HTML, je mets en forme ce tag avec des data- supplémentaires sur le tag html parent
221
      while (match = this.abeAsAttributePattern.exec(block)) {
222
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length)
223
      }  
224
    })
225
226
    return this
227
  }
228
229
  /**
230
   * create an array of {{#each}} blocks from the html document
231
   * @param  {String} html the html document
0 ignored issues
show
Documentation introduced by
The parameter html does not exist. Did you maybe forget to remove this comment?
Loading history...
232
   * @return {Array}      the array of {{#each}} blocks
233
   */
234
  _splitEachBlocks() {
235
    var block
236
    var blocks = []
237
238
    while (block = this.blockPattern.exec(this.template)) {
239
      blocks.push(block[1])
240
    }
241
242
    return blocks
243
  }
244
245
  _insertAbeEach(theMatch, key, lastIndex) {
0 ignored issues
show
Unused Code introduced by
The parameter lastIndex is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
246
    var matchBlock = theMatch[0]
247
    if(cmsData.regex.isEachStatement(matchBlock)) return
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
248
    if(cmsData.regex.isBlockAbe(matchBlock)){
249
      var matchblockattr = (matchBlock.split('=')[0]).trim()
250
      var getattr = cmsData.regex.getAttr(matchBlock, 'key').replace('.', '[index].')
251
      var newMatchBlock = ((!this._onlyHTML) ?
252
                            (/=[\"\']\{\{(.*?)\}\}/g.test(matchBlock) ?
253
                                ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + matchblockattr + '"' :
254
                                '') +
255
                            ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="' + getattr + '" ' + matchBlock :
256
                            matchBlock)
257
          .replace(new RegExp('(key=[\'|"])' + key + '.', 'g'), '$1' + key + '[index].')
258
          .replace(/\{\{abe/, '{{abe dictionnary=\'' + key + '\'')
259
260
      this.template = this.template.replace(matchBlock, newMatchBlock)
261
    }
262
263
    return this
264
  }
265
266
  /**
267
   * add <abe> tag around html tag
268
   */
269
  _removeHidden() {
270
    this.template = this.template.replace(/(\{\{abe.*visible=[\'|\"]false.*\}\})/g, '')
271
272
    return this
273
  }
274
275
  /**
276
   * add <abe> tag around html tag
277
   * @param {String} text html string
0 ignored issues
show
Documentation introduced by
The parameter text does not exist. Did you maybe forget to remove this comment?
Loading history...
278
   */
279
  _encloseAbeTag() {
280
    var match
281
    while (match = this.abePattern.exec(this.template)) {
282
      this.template = this.template.replace(cmsData.regex.escapeTextToRegex(match[1], 'g'), '<abe>' + match[1].trim() + '</abe>')
283
    }
284
285
    return this
286
  }
287
288
  _addSource(json) {
289
    var listReg = /({{abe.*type=[\'|\"]data.*}})/g
290
    var match
291
292
    while (match = listReg.exec(this.template)) {
293
      var editable = cmsData.regex.getAttr(match[0], 'editable')
294
      var key = cmsData.regex.getAttr(match[0], 'key')
295
296
      if((typeof editable === 'undefined' || editable === null || editable === '' || editable === 'false')
297
        && typeof json[config.source.name] !== 'undefined' && json[config.source.name] !== null) {
298
        json[key] = json[config.source.name][key]
299
      }
300
301
      json = Hooks.instance.trigger('afterAddSourcePage', json, match[0])
302
    }
303
  }
304
}